﻿using System.Linq;
using DeltaGroup.WheelOfJeopardy.GameManager;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using DeltaGroup.WheelOfJeopardy.Model;
using System.Collections.Generic;
using DeltaGroup.WheelOfJeopardy.UI.Events;
using Moq;
using WoJ_UT_Mike.Stub;

namespace WoJ_UT_Mike.GameManagerTests
{


    /// <summary>
    ///This is a test class for NormalRoundTest and is intended
    ///to contain all NormalRoundTest Unit Tests
    ///</summary>
    [TestClass()]
    public class NormalRoundTest
    {
        private Mock<INormalRoundUIManager> _uiManager;
        private Mock<IChallengeManager> _challengeManager;
        private StubGameSettings _gameSetting;
        private NormalRound _round;
        private List<Category> _categories;
        private List<Contestant> _contestants;
        
        [TestInitialize]
        public void Initialize()
        {
            _uiManager = new Mock<INormalRoundUIManager>();
            _challengeManager = new Mock<IChallengeManager>();
            _gameSetting = new StubGameSettings();
            _contestants = new List<Contestant>()
                               {
                                   new Contestant(){Name = "Terry", FreeTurnCount = 0, IsPlaying = false, Score = 0},
                                   new Contestant(){Name = "Mike", FreeTurnCount = 0, IsPlaying = false, Score = 0},
                                   new Contestant(){Name = "Kyle", FreeTurnCount = 0, IsPlaying = false, Score = 0}
                               };
            _categories = new List<Category>();
            for (var i = 0; i < 6; i++)
            {
                var category = new Category {Title = "test" + i, Challenges = new List<Challenge>()};
                for (var j = 0; j < 5; j++)
                {
                    var challenge = new Challenge {Point = (j + 1)*100, Prompt = "test prompt " + j, Category = category.Title};
                    category.Challenges.Add(challenge);
                }
                _categories.Add(category);
            }

            _round = new NormalRound(_gameSetting, _categories, _contestants, DifficultyLevel.Easy) { UIManager = _uiManager.Object, ChallengeManager = _challengeManager.Object};
            _round.Start();
        }
        [TestMethod]
        public void On_Start_Set_First_Contestant_To_Play()
        {
            Assert.IsTrue(_contestants[0].IsPlaying);
        }
        [TestMethod]
        public void On_Start_Set_First_Contestant_Spin_Wheel()
        {
            _uiManager.Verify(x => x.PromptToSpin());
        }

        [TestMethod]
        public void On_Bankrupt_Sector_Current_Contestant_Should_Lose_All_Score()
        {
            var currentPlayer = _contestants.First();
            currentPlayer.Score = 100;
            
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new BankruptSector()));

            Assert.AreEqual(0, currentPlayer.Score);
        }
        [TestMethod]
        public void On_Bankrupt_Sector_Current_Contestant_Will_Lose_Turn()
        {
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new BankruptSector()));

            Assert.IsFalse(_contestants[0].IsPlaying);
            Assert.IsTrue(_contestants[1].IsPlaying);
        }
        [TestMethod]
        public void On_Category_Sector_First_Unmarked_Challenge_Is_Marked_As_Challenged()
        {
            var firstUnmarkedChallenge = _categories.First(x => x.Title == "test1").Challenges.First(x => x.IsChallenged == false);
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test1")));

            Assert.IsTrue(firstUnmarkedChallenge.IsChallenged);
        }
        [TestMethod]
        public void On_Category_Sector_Display_Challenge_To_UI()
        {
            var firstUnmarkedChallenge = _categories.First(x => x.Title == "test1").Challenges.First(x => x.IsChallenged == false);
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test1")));

            _uiManager.Verify(x => x.PromptForResponse(firstUnmarkedChallenge, 60));
        }
        [TestMethod]
        public void On_Category_Sector_Spin_Again_If_Category_Has_No_More_Challenges()
        {
            foreach (var challenge in _categories.First(x => x.Title == "test1").Challenges)
            {
                challenge.IsChallenged = true;
            } 
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test1")));

            _uiManager.Verify(x => x.PromptToSpin());
        }

        [TestMethod]
        public void On_Free_Turn_Sector_Current_Contestant_Is_Granted_Free_Turn()
        {
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new FreeTurnSector()));

            Assert.AreEqual(1, _contestants.First().FreeTurnCount);
        }
        [TestMethod]
        public void On_Free_Turn_Sector_Current_Contestant_Keep_Playing()
        {
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new FreeTurnSector()));

            Assert.IsTrue(_contestants.First().IsPlaying);
            _uiManager.Verify(x => x.PromptToSpin());
        }
        [TestMethod]
        public void On_Lose_Turn_Sector_Current_Contestant_Loses_Turn()
        {
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new LoseTurnSector()));

            Assert.IsFalse(_contestants[0].IsPlaying);
            Assert.IsTrue(_contestants[1].IsPlaying);
            _uiManager.Verify(x => x.PromptToSpin());
        }
        [TestMethod]
        public void On_Players_Choice_Sector_Tell_UI_To_Show_Unfinished_Categories_For_Selection()
        {
            var finishedCategories = from c in _categories
                                     where new[] {"test1", "test2"}.Contains(c.Title)
                                     select c;
            foreach (var category in finishedCategories)
            {
                category.Challenges.ForEach(x => x.IsChallenged = true);
            }
            List<Category> categoriesAllowed = null;
            _uiManager.Setup(x => x.PromptToSelectCategory(It.IsAny<List<Category>>())).Callback<List<Category>>((cs) => categoriesAllowed = cs);

            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new PlayersChoiceSector()));
            
            Assert.IsNotNull(categoriesAllowed);
            Assert.AreEqual(4, categoriesAllowed.Count);

        }
        [TestMethod]
        public void On_Opponents_Choice_Sector_Tell_UI_To_Show_Unfinished_Categories_For_Selection()
        {
            var finishedCategories = from c in _categories
                                     where new[] { "test1", "test2" }.Contains(c.Title)
                                     select c;
            foreach (var category in finishedCategories)
            {
                category.Challenges.ForEach(x => x.IsChallenged = true);
            }
            List<Category> categoriesAllowed = null;
            _uiManager.Setup(x => x.PromptToSelectCategory(It.IsAny<List<Category>>())).Callback<List<Category>>((cs) => categoriesAllowed = cs);

            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new OpponentsChoiceSector()));

            Assert.IsNotNull(categoriesAllowed);
            Assert.AreEqual(4, categoriesAllowed.Count);

        }

        [TestMethod]
        public void On_Spin_Again_Sector_Current_Contestant_Stay_In_Turn()
        {
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new SpinAgainSector()));

            Assert.IsTrue(_contestants.First().IsPlaying);
            _uiManager.Verify(x => x.PromptToSpin());
        }

        [TestMethod]
        public void Ask_For_Wager_When_Daily_Double_Is_Selected()
        {
            SetDailyDouble();

            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test0")));

            _uiManager.Verify(x => x.PromptForWager("test0", It.IsAny<int>()));
        }
        [TestMethod]
        public void Daily_Double_Wager_Maximum_Is_Contestant_Score()
        {
            _contestants[0].Score = 2000;

            SetDailyDouble();

            _uiManager.Setup(x => x.PromptForWager("test0", It.IsAny<int>())).Callback<string,int>((c,s) => Assert.AreEqual(2000, s));

            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test0")));

            _uiManager.Verify();
        }
        [TestMethod]
        public void Daily_Double_Wager_Maximum_Is_Round_Maximum()
        {
            _contestants[0].Score = 0;

            SetDailyDouble();

            _uiManager.Setup(x => x.PromptForWager("test0", It.IsAny<int>())).Callback<string, int>((c, s) => Assert.AreEqual(500, s));

            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test0")));

            _uiManager.Verify();
        }

        private void SetDailyDouble()
        {
            var category = _categories.First();
            var candidate = category.Challenges[0];
            var dd = new DailyDouble(candidate);
            category.Challenges.Remove(candidate);
            category.Challenges.Insert(0, dd);
        }

        [TestMethod]
        public void On_Category_Selected_Display_First_Unmarked_Challenge()
        {
            _uiManager.Raise(x => x.CategorySelected += null, new CategorySelectedEventArgs(new Category(){Title = "test0"}));

            _uiManager.Verify(x => x.PromptForResponse(_categories[0].Challenges.First(), 60));
        }

        [TestMethod]
        public void On_Losing_Turn_Ask_Contestant_To_Use_Free_Turn()
        {
            _contestants[0].FreeTurnCount = 1;

            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new LoseTurnSector()));

            _uiManager.Verify(x => x.PromptForFreeTurnUsage(1));
        }

        [TestMethod]
        public void On_Using_Free_Turn_Deduct_Free_Turn_Count()
        {
            _contestants[0].FreeTurnCount = 2;

            _uiManager.Raise(x => x.UseFreeTurn += null, new EventArgs());

            Assert.AreEqual(1, _contestants[0].FreeTurnCount);
        }
        [TestMethod]
        public void On_Using_Free_Turn_Contestant_Stay_In_Turn()
        {
            _contestants[0].FreeTurnCount = 1;

            _uiManager.Raise(x => x.UseFreeTurn += null, new EventArgs());

            _contestants[0].IsPlaying = true;
            _uiManager.Verify(x => x.PromptToSpin());
        }
        [TestMethod]
        public void On_Not_Using_Free_Turn_Next_Contestant_Take_Turn()
        {
            _uiManager.Raise(x => x.NotUseFreeTurn += null, new EventArgs());

            _contestants[1].IsPlaying = false;
            _uiManager.Verify(x => x.PromptToSpin());
        }
        [TestMethod]
        public void On_Correct_Response_Award_Point()
        {
            _challengeManager.Setup(x => x.IsResponseCorrect(It.IsAny<DifficultyLevel>(), It.IsAny<Challenge>(), "test")).Returns(true);
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test0")));
            _uiManager.Raise(x => x.ChallengeResponseReceived += null, new ChallengeResponseReceivedEventArgs("test"));

            Assert.AreEqual(100, _contestants[0].Score);
        }
        [TestMethod]
        public void On_Correct_Response_Stay_In_Turn()
        {
            _challengeManager.Setup(x => x.IsResponseCorrect(It.IsAny<DifficultyLevel>(), It.IsAny<Challenge>(), "test")).Returns(true);
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test0")));
            _uiManager.Raise(x => x.ChallengeResponseReceived += null, new ChallengeResponseReceivedEventArgs("test"));

            Assert.IsTrue(_contestants[0].IsPlaying);
            _uiManager.Verify(x => x.PromptToSpin());
        }

        [TestMethod]
        public void On_Incorrect_Response_Lose_Turn()
        {
            _challengeManager.Setup(x => x.IsResponseCorrect(It.IsAny<DifficultyLevel>(), It.IsAny<Challenge>(), It.IsAny<string>())).Returns(false);
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test0")));
            _uiManager.Raise(x => x.ChallengeResponseReceived += null, new ChallengeResponseReceivedEventArgs("test"));

            Assert.IsFalse(_contestants[0].IsPlaying);
            Assert.IsTrue(_contestants[1].IsPlaying);
            _uiManager.Verify(x => x.PromptToSpin());
        }

        [TestMethod]
        public void On_No_Response_Lose_Turn()
        {
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test0")));
            _uiManager.Raise(x => x.ResponseTimerExpired += null, new EventArgs());

            Assert.IsFalse(_contestants[0].IsPlaying);
            Assert.IsTrue(_contestants[1].IsPlaying);
            _uiManager.Verify(x => x.PromptToSpin());
        }

        [TestMethod]
        public void On_No_Response_Contestant_Lose_No_Point()
        {
            _contestants[0].Score = 200;

            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test0")));
            _uiManager.Raise(x => x.ResponseTimerExpired += null, new EventArgs());

            Assert.AreEqual(200, _contestants[0].Score);
        }
        [TestMethod]
        public void Correct_Response_On_Daily_Double_Challenge_Receives_Wager_Point()
        {
            _contestants[0].Score = 2000;
            _challengeManager.Setup(x => x.IsResponseCorrect(It.IsAny<DifficultyLevel>(), It.IsAny<Challenge>(), It.IsAny<string>())).Returns(true);
            SetDailyDouble();
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new CategorySector("test0")));
            _uiManager.Raise(x => x.WagerReceived += null, new WagerReceivedEventArgs(2000));
            _uiManager.Raise(x => x.ChallengeResponseReceived += null, new ChallengeResponseReceivedEventArgs("test"));

            Assert.AreEqual(4000, _contestants[0].Score);

        }
        [TestMethod]
        public void Ends_Round_When_No_More_Spin_Left()
        {
            var roundEnded = false;
            _round.Finished += (sender, args) => { roundEnded = true; };

            for (int i = 0; i < _gameSetting.SpinsPerNormalRound; i++)
            {
                _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new LoseTurnSector()));
            }
            
            Assert.IsTrue(roundEnded);
        }
        [TestMethod]
        public void Ends_Round_When_No_More_Challenge_Left()
        {
            var roundEnded = false;
            _round.Finished += (sender, args) => { roundEnded = true; };

            var challenges = _categories.SelectMany(x => x.Challenges);

            foreach (var challenge in challenges)
            {
                challenge.IsChallenged = true;
            }
            _uiManager.Raise(x => x.SectorSelected += null, new SectorSelectedEventArgs(new LoseTurnSector()));

            Assert.IsTrue(roundEnded);
        }
    }
}
